#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <ctype.h>

/*Define the trace macro's */
#define LOG_INFO(format, ...)		   	printf(stderr, format, ## __VA_ARGS__)
#define LOG_ERROR(format, ...)		  	fprintf(stderr, format, ## __VA_ARGS__)

//#define PRINT_FILE
//#define PRINT_LINE_NUMBER

#ifdef PRINT_LINE_NUMBER
	#define LOG_INFO_LINE_PRINT(format, ...)  printf(format, ## __VA_ARGS__)
#else
	#define LOG_INFO_LINE_PRINT(format, ...)
#endif

#ifdef PRINT_FILE
	#define LOG_INFO_FILE_PRINT(format, ...)  printf(format, ## __VA_ARGS__)
#else
	#define LOG_INFO_FILE_PRINT(format, ...)
#endif

#ifdef DEBUG_MODE
	#define LOG_INFO_DEBUG(format, ...)		 fprintf(stderr, format, ## __VA_ARGS__)
#else
	#define LOG_INFO_DEBUG(format, ...)
#endif

/*some useful macros */
#define MAX_BUFFER_SIZE			 1024
#define MAX_DATA_TYPES			 100
#define NOT_REGULAR_FILE(x)		 !(x & S_IFREG)
#define true 					 1
#define false					 0


/*some userful typedef */
typedef unsigned int uint32_t;
typedef unsigned char uint8_t;
typedef unsigned short int  uint16_t;
typedef unsigned int bool;

typedef enum _TYPE
{
	DATA_TYPE_STRUCT = 0,
	DATA_TYPE_ENUM,
	DATA_TYPE_TYPE_DEF,
	DATA_TYPE_FUNCTION,
	DATA_TYPE_LAST,
}TYPE;


typedef struct _data_type
{
	char 	name[MAX_BUFFER_SIZE];
	TYPE	type;
	uint32_t lineNumber;
}DATA_TYPE;

/*declaration of struct.. */
typedef struct _parser_info_
{
	/*Information of data Source */
	char 		fileName[MAX_BUFFER_SIZE];
	FILE 		*fsource;
	uint32_t 	lineNumber;

	/*Parser state */
	int			bracesCount;					/*Depth of braces... '{' */
	int		 	bracketCount;					/*Depth of Brackets.... '('  */
	uint32_t 	parser_state;
	bool 		type_typedef, type_struct, type_enum;
	bool 		insideBracket;
	bool 		insideBraces;					/*Value is false if we are in global space */
	char 		prevString[MAX_BUFFER_SIZE]; 	/*Contains the prev identifier of keyword encountered */
	bool 		expectFunction;

	/*Parser information */
	DATA_TYPE 	userDefinedDataTypes[MAX_DATA_TYPES];
	uint32_t	numDataTypes;

	/*temp variables */
	char 		ch, ch1;
	char 		buffer[MAX_BUFFER_SIZE];
	uint32_t 	buffer_idx;
}PARSER_INFO;


static const char *type_str[] = { "struct",  "enum", "typedef", "function" };

/*function Prototypes.. */
int  	check_file(const char *filename);
void 	init_parser_info(PARSER_INFO *p, const char *filename);
void 	free_parser_info(PARSER_INFO *p);
void 	remove_cpp_comments(PARSER_INFO *cfile);
void 	remove_c_comments(PARSER_INFO *cfile);
int  	processString(PARSER_INFO *cfile, const char type);
bool 	isKeyword(const char *buffer);
int  	processIndentifierOrKeyword(PARSER_INFO *cfile);
int  	processPreprocessorDirective(PARSER_INFO *cfile);
int  	processNumber(PARSER_INFO *cfile);
bool    AddUserDefinedDataType(PARSER_INFO *cfile, const char *dataType, TYPE type);
bool    CheckUserDefinedDataType(PARSER_INFO *cfile, const char *dataType);


/****************************************************************
 * This function performs the acutal Analyses of the C file...
 ***************************************************************/
int AnalyseCFile(PARSER_INFO *cfile)
{
	LOG_INFO_DEBUG("Opening file '%s'\n", cfile->fileName);
	cfile->fsource = fopen(cfile->fileName, "r");
	if(NULL == cfile->fsource)
	{
		LOG_ERROR("unable to open file '%s'\n", cfile->fileName);
		return 1;
	}

	cfile->lineNumber = 1;
	LOG_INFO_LINE_PRINT("%d ", cfile->lineNumber);
	/*start reading the file... */
	cfile->ch = fgetc(cfile->fsource);
	while(!feof(cfile->fsource))
	{
		if('/' == cfile->ch)
		{
			cfile->ch1 = fgetc(cfile->fsource);
			if('*' == cfile->ch1)
				remove_c_comments(cfile);
			else if( '/' == cfile->ch1 )
				remove_cpp_comments(cfile);
			else
			{
				LOG_INFO_FILE_PRINT("%c", cfile->ch);
				ungetc(cfile->ch1, cfile->fsource);
			}
		}
		else if( ('"' == cfile->ch) || ('\'' == cfile->ch)) /*If its a string... */
		{
			processString(cfile, cfile->ch);
		}
		else if(isalpha(cfile->ch) || ('_' == cfile->ch)) /*check wheter if its a keyword,data type (struct/enum/typedef)  of variable/function name */
		{
			processIndentifierOrKeyword(cfile);
		}
		else if('#' == cfile->ch)
		{
			processPreprocessorDirective(cfile);
		}
		else if(isdigit(cfile->ch))
		{
			processNumber(cfile);
		}
		else if( (false == cfile->insideBraces) && (';' == cfile->ch ))
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);

			if((false == cfile->expectFunction) && (false == cfile->insideBraces) && (false == cfile->insideBracket) && (false == cfile->type_typedef))
			{
				printf("\nvariable? = %s:%d\n", cfile->buffer, cfile->lineNumber);
			}
			else if((false == cfile->insideBraces) && (true == cfile->expectFunction) )
			{
				cfile->expectFunction = false;
			}

			if(true == cfile->type_typedef)
			{
				/*Add the identifier to DB it is preceded by typedef */

				AddUserDefinedDataType(cfile, cfile->buffer, DATA_TYPE_TYPE_DEF);
				cfile->type_typedef = false;
			}

			/*This is variable declaration of type "struct xxx AAA;" Or "typedef struct xxx zzz; " */
			cfile->type_struct = false;
		}
		else if('{' == cfile->ch)
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);

			if((true == cfile->expectFunction) && (0 != strlen(cfile->prevString)))
			{
				AddUserDefinedDataType(cfile,cfile->prevString,DATA_TYPE_FUNCTION);
			}
			else if((true == cfile->type_struct) && (false == cfile->expectFunction) && (false == cfile->insideBraces) )
			{
				AddUserDefinedDataType(cfile, cfile->prevString, (false == cfile->type_enum) ?  DATA_TYPE_STRUCT: DATA_TYPE_ENUM);
				cfile->type_struct = false;
				cfile->type_enum = false;
			};
			/*Do not put an else here!!! */
			if(false == cfile->insideBraces)
			{
				cfile->bracesCount = 0;
			}
			cfile->insideBraces = true;
			cfile->expectFunction = false;
			cfile->bracesCount++;
		}
		else if('}' == cfile->ch)
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
			cfile->bracesCount--;
			if(0 == cfile->bracesCount)
			{
				cfile->insideBraces = false;
			}
		}
		else if('(' == cfile->ch)
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
			if(0 == cfile->bracesCount)
				cfile->expectFunction = true;
			cfile->insideBracket = true;
			cfile->bracketCount++;
		}
		else if( ')' == cfile->ch )
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
			cfile->bracketCount--;
			if(0 == cfile->bracketCount)
				cfile->insideBracket = false;
		}
		else if(('=' == cfile->ch) && (false == cfile->insideBraces))
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);			
			cfile->type_struct = false;
		}
		else
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
			if('\n' == cfile->ch)
			{
				cfile->lineNumber++;
				LOG_INFO_LINE_PRINT("%d ", cfile->lineNumber);
			}
		}
		cfile->ch = fgetc(cfile->fsource);
	}

	LOG_INFO_DEBUG("\nNumber of Lines Processed : %d\n", cfile->lineNumber);
	return 0;
}

/*
 * Main Function.. (Nothing much to tell about it!)
 * */
int main(int argc, char *argv[])
{
	PARSER_INFO cfile;
	if(2 != argc)
	{
		LOG_ERROR("Usage %s <fileName>\n", argv[0]);
		exit(1);
	}

	malloc(-1);
	if(0 == check_file(argv[1]))
	{
		init_parser_info(&cfile, argv[1]);
		AnalyseCFile(&cfile);
		free_parser_info(&cfile);
	}
	return 0;
}

/***********************************************************
 * This function checks if the file is regular file and
 * its 'C' file or not
 ************************************************************/
int check_file(const char *filename)
{
	int returnValue = 0;
	struct stat info;
	returnValue = stat(filename, &info);
	if( 0 != returnValue )
	{
		LOG_ERROR("unable to open file %s\n", filename);
		returnValue = 1;
	}
	else if( NOT_REGULAR_FILE(info.st_mode) )
	{
		LOG_ERROR("file '%s' is not regular file\n", filename);
		returnValue = 2;
	}
	else if( NULL == strstr(filename, ".c" ) )
	{
		LOG_ERROR("file '%s' is not a CFile\n", filename);
		returnValue = 3;
	}
	return returnValue;
}

void free_parser_info(PARSER_INFO *cfile)
{
	uint32_t i = 0;

	for(i = 0; i < cfile->numDataTypes; i++)
	{
			LOG_ERROR("\n %s %d %s", type_str[cfile->userDefinedDataTypes[i].type], cfile->userDefinedDataTypes[i].lineNumber,
									 cfile->userDefinedDataTypes[i].name );
	}

	return;
}
/*Struct Initialiation functions */
void init_parser_info(PARSER_INFO *p, const char *filename)
{

	//initialise the parameters of PARSER_INFO

	memset(p->fileName, 0, MAX_BUFFER_SIZE);
	strcpy(p->fileName, filename);
	memset(p->buffer, 0, MAX_BUFFER_SIZE);
	p->bracesCount = p->bracketCount = 0;
	p->lineNumber = 0;
	p->fsource = 0;
	p->buffer_idx = 0;
	p->type_typedef = p->type_struct  =  p->type_enum = false;
	p->insideBraces = p->insideBracket = false;
	p->expectFunction = false;
	p->numDataTypes = 0;

}

void remove_cpp_comments(PARSER_INFO *cfile)
{
	while(!feof(cfile->fsource))
	{
		cfile->ch = fgetc(cfile->fsource);
		if('\n' == cfile->ch)
		{
			LOG_INFO_FILE_PRINT("\n"); /* To maintain consistency between line number of source and destination*/
			cfile->lineNumber++;
			break;
		}
	}
	return;
}
void remove_c_comments(PARSER_INFO *cfile)
{
	while(!feof(cfile->fsource))
	{
		cfile->ch = fgetc(cfile->fsource);
		if('*' == cfile->ch)
		{
			cfile->ch1 = fgetc(cfile->fsource);
			if('/' == cfile->ch1)
				break;
			ungetc(cfile->ch1, cfile->fsource);
		}
		else if('\n' == cfile->ch)
		{
			LOG_INFO_FILE_PRINT("\n"); /* To maintain consistency between line number of source and destination*/
			cfile->lineNumber++;
		}
	}
	return;
}
int processString(PARSER_INFO *cfile, const char type)
{
	LOG_INFO_FILE_PRINT("%c", cfile->ch); /*Start of the string (double or single quote*/
	while(!feof(cfile->fsource))
	{
		cfile->ch = fgetc(cfile->fsource);
		if(type == cfile->ch)
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch); /*End of the string...*/
			break;
		}
		else if('\\' == cfile->ch)
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
			cfile->ch = fgetc(cfile->fsource);
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
			if('\n' == cfile->ch)
			{
				cfile->lineNumber++;
				LOG_INFO_FILE_PRINT("%d", cfile->lineNumber);
			}
		}
		else if('\n' == cfile->ch)
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
			cfile->lineNumber++;
			LOG_INFO_FILE_PRINT("%d", cfile->lineNumber);
		}
		else
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
		}
	}
	return 0;
}
bool isKeyword(const char *buffer)
{
	uint32_t i = 0;
	bool returnValue = false;
	const char *keyWords[] = { "int", 	"char", 	"long", 	"double" ,	"float", 	"bool", 	"short", 	"short", "unsigned",
						 	   "while", "do", 		"for", 		"if", 		"else",		"default", 	"continue", "break", "goto", 	"case", "switch", 	"return",
						 	   "void", 	"const", 	"typedef", 	"register", "struct", 	"enum", 	"sizeof", 	"union", "extern", 	"auto", "volatile"
							 };
	const uint32_t keywordsSize = sizeof(keyWords)/sizeof(*keyWords);

	for(i = 0; i < keywordsSize; i++)
	{
		if(0 == strcmp(keyWords[i], buffer))
		{
			returnValue = true;
			break;
		}
	}
	return returnValue;
}

int processIndentifierOrKeyword(PARSER_INFO *cfile)
{
	cfile->buffer_idx = 0;
	memset(cfile->buffer, 0, MAX_BUFFER_SIZE);
	do
	{
		cfile->buffer[cfile->buffer_idx++] = cfile->ch;
		cfile->ch = fgetc(cfile->fsource);
	}
	while((!feof(cfile->fsource)) && (isalpha(cfile->ch) || isdigit(cfile->ch) || ('_' == cfile->ch)));
	ungetc(cfile->ch, cfile->fsource);

	/*Create a copy of this Identifier only if we are not inside (), So we can keep track of functionNames */
	if(false == cfile->expectFunction)
		strcpy(cfile->prevString, cfile->buffer);

	/*see if this is a user defined data type such as typedef, enums, structs etc.. */
	if(CheckUserDefinedDataType(cfile, cfile->buffer))
	{
		LOG_INFO_FILE_PRINT("%s", cfile->buffer);
	}
	else if(isKeyword(cfile->buffer))
	{
		LOG_INFO_FILE_PRINT("%s", cfile->buffer);
		/*Check for Typedef only if we are in global space */
		if((0 == strcmp(cfile->buffer, "typedef")) && (false == cfile->insideBraces))
		{
			cfile->type_typedef	= true;
		}
		else if(((0 == strcmp(cfile->buffer, "struct")) ||(0 == strcmp(cfile->buffer, "enum"))) && (false == cfile->insideBraces))
		{
			cfile->type_struct = true;
			cfile->type_enum = true;
		}
	}
	else
		LOG_INFO_FILE_PRINT("%s", cfile->buffer);

	return 0;
}


bool AddUserDefinedDataType(PARSER_INFO *cfile, const char *dataType, TYPE type)
{
	bool returnValue = false;

	/*Add the User Defined dataType to DB if its not a keyword */
	if((MAX_DATA_TYPES > cfile->numDataTypes) && !isKeyword(dataType))
	{

		strcpy(cfile->userDefinedDataTypes[cfile->numDataTypes].name, dataType);
		cfile->userDefinedDataTypes[cfile->numDataTypes].type = type;
		cfile->userDefinedDataTypes[cfile->numDataTypes].lineNumber = cfile->lineNumber;
		cfile->numDataTypes++;
		returnValue = true;

	}
	return returnValue;
}

bool CheckUserDefinedDataType(PARSER_INFO *cfile, const char *dataType)
{
	bool returnValue = false;
	uint32_t i = 0;

	for(i = 0; i < cfile->numDataTypes; i++)
	{
		if(0 == strcmp(cfile->userDefinedDataTypes[i].name, dataType))
		{
			returnValue = true;
			break;
		}
	}
	return returnValue;
}

int processPreprocessorDirective(PARSER_INFO *cfile)
{
	uint32_t i = 0;
	char str[MAX_BUFFER_SIZE];
	LOG_INFO_FILE_PRINT("#"); /*start of the preprocessor*/

	while(!feof(cfile->fsource))
	{
		cfile->ch = fgetc(cfile->fsource);
		if(!isalpha(cfile->ch))
			break;
		str[i++] = cfile->ch;
	}
	str[i] = 0;
	if(strcmp(str, "define") == 0)
	{
		i = 0;
		while(!feof(cfile->fsource))
		{
			cfile->ch = fgetc(cfile->fsource);
			if((' ' == cfile->ch) || ('\t' == cfile->ch) || ('(' == cfile->ch))
				break;
			str[i++] = cfile->ch;
		}
		str[i] = 0;	
		printf("\nMacro : %s", str);
	}
	while(!feof(cfile->fsource))
	{
		cfile->ch = fgetc(cfile->fsource);
		if('\\' == cfile->ch)
		{
			do
			{
				LOG_INFO_FILE_PRINT("%c", cfile->ch);
				cfile->ch = fgetc(cfile->fsource);
			}
			while(' ' == cfile->ch) ;
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
			if('\n' == cfile->ch)
			{
				cfile->lineNumber++;
				LOG_INFO_LINE_PRINT ("%d ", cfile->lineNumber);
			}
		}
		else if('\n' == cfile->ch)
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch); /*End of the preprocessor */
			cfile->lineNumber++;
			LOG_INFO_LINE_PRINT("%d ", cfile->lineNumber);
			break;
		}
		else
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch);
		}

	}
	return 0;
}

int processNumber(PARSER_INFO *cfile)
{
	bool hex = false, octal = false;
	LOG_INFO_FILE_PRINT("%c", cfile->ch); /*start of the Number*/
	if('0' == cfile->ch)
	{
		octal = true;
		cfile->ch1 = fgetc(cfile->fsource);
		if('X' == toupper(cfile->ch1))
		{
			hex = true;
			octal = false;
			LOG_INFO_FILE_PRINT("%c", cfile->ch1);
		}
		else if(isdigit(cfile->ch1))
		{
			LOG_INFO_FILE_PRINT("%c", cfile->ch1);
		}
		else
		{
			/*The character does not belong to the number hence push it back */
			ungetc(cfile->ch1, cfile->fsource);
			return 0;
		}
	}

	while(!feof(cfile->fsource))
	{
		cfile->ch = fgetc(cfile->fsource);
		if(!(isdigit(cfile->ch) || (hex && ('A' <= toupper(cfile->ch)) && ('F' >= toupper(cfile->ch)))))
		{
			LOG_INFO_FILE_PRINT(" "); /*End  of the Number*/
			/*The character does not belong to the number hence push it back */
			ungetc(cfile->ch, cfile->fsource);
			break;
		}
		LOG_INFO_FILE_PRINT("%c", cfile->ch); /*start of the Number*/
	}

	return 0;
}

//End of program
